﻿using System;
using System.Collections.Generic;
using System.Linq;
using SharpCart.Utils.Security;
using System.Configuration;
using System.Text.RegularExpressions;

namespace SharpCart.BusinessObjects
{
    public abstract class CreditCard
    {
        #region Declarations
        internal Data.UserCreditCard DataCard;
        internal  Data.SharpDb Db;
        protected bool IsNew;
        #endregion

        #region Properties
        /// <summary>
        /// The id assigned to the card by the database.
        /// </summary>
        public int ID
        {
            get
            {
                return DataCard.ID;
            }
        }

        /// <summary>
        /// The card number.  When the card number is read, only the last
        /// four digits are returned.  The rest of the card number is 
        /// replaced with X's.
        /// </summary>
        public string CardNumber
        {
            get
            {
                string FullNumber = this.ChargeCardNumber;
                string Result = string.Empty;
                for (int i = 0; i < FullNumber.Length - 4; i++)
                {
                    Result += "X";
                }
                Result += FullNumber.Substring(FullNumber.Length - 4);
                return Result;
            }
            set
            {
                if (IsValidCardNumber(value))
                {
                    byte[] EncryptionKey = System.Text.UTF8Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["Key"]);
                    byte[] IV = System.Text.UTF8Encoding.UTF8.GetBytes(Utils.Constants.IV);
                    string EncryptedCardNumber = Encryption.Encrypt(value, EncryptionKey, IV);
                    DataCard.CardNumber = value;
                }
                else
                {
                    throw new ArgumentException("Invalid card number", CardNumber);
                }
            }
        }

        /// <summary>
        /// Read only access to the complete card number.
        /// </summary>
        internal string ChargeCardNumber
        {
            get
            {
                byte[] EncryptionKey = System.Text.UTF8Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["Key"]);
                byte[] IV = System.Text.UTF8Encoding.UTF8.GetBytes(Utils.Constants.IV);
                string DecryptedNumber = Utils.Security.Encryption.Decrypt(DataCard.CardNumber, EncryptionKey, IV);
                return DecryptedNumber;
            }
        }

        /// <summary>
        /// The month that the card expires.  Valid values are 1-12.
        /// </summary>
        public int ExpireMonth
        {
            get
            {
                return DataCard.ExpireMonth;
            }
            set
            {
                if (value <= 12 && value >= 1)
                {
                    DataCard.ExpireMonth = value;
                }
                else
                {
                    throw new ArgumentException("Invalid expiration month.", "ExpireMonth");
                }
            }
        }

        /// <summary>
        /// The year that the card expires.
        /// </summary>
        public int ExpireYear
        {
            get
            {
                return DataCard.ExpireYear;
            }
            set
            {
                DataCard.ExpireYear = value;
            }
        }

        /// <summary>
        /// The user that owns the card.
        /// </summary>
        public User Owner
        {
            get
            {
                if (!DataCard.UserReference.IsLoaded)
                {
                    DataCard.UserReference.Load();
                }
                return new User(DataCard.User, Db);
            }
        }

        /// <summary>
        /// The name that appears on the card.
        /// </summary>
        public string NameOnCard
        {
            get
            {
                return DataCard.NameOnCard;
            }
            set
            {
                DataCard.NameOnCard = value;
            }
        }

        /// <summary>
        /// The ZIP code used for billing.
        /// </summary>
        public string BillingZip
        {
            get
            {
                return DataCard.BillingZip;
            }
            set
            {
                string pattern = @"^(\d{5}-\d{4}|\d{5}|\d{9})$";
                Regex match = new Regex(pattern);
                if (match.IsMatch(value))
                {
                    DataCard.BillingZip = value;
                }
                else
                {
                    throw new ArgumentException("Invalid zip code " + value + ".", "BillingZip");
                }
            }
        }
        #endregion

        #region Abstract Methods
        protected abstract bool IsValidCardNumber(string CardNumber);
        #endregion

        #region Inherited Methods
        /// <summary>
        /// Runs a Luhn check on a supplied card number
        /// </summary>
        /// <param name="CardNumber">The card number to check</param>
        /// <returns>True if valid, false otherwise.</returns>
        protected virtual bool IsLuhnValid(string CardNumber)
        {
            bool Result = true;
            int Sum = 0;
            int CurrentDigit = 0;
            int Addend = 0;
            bool doMultiply = false;
            for (int i = CardNumber.Length - 1; i > 0; i--)
            {
                if (int.TryParse(CardNumber[i].ToString(), out CurrentDigit))
                {
                    if (doMultiply)
                    {
                        Addend = CurrentDigit * 2;
                        if (Addend > 9)
                        {
                            Addend -= 9;
                        }
                    }
                    else
                    {
                        Addend = CurrentDigit;
                    }
                }
                else
                {
                    Result = false;
                    break;
                }
                Sum += Addend;
                doMultiply = !doMultiply;
            }
            if (Result)
            {
                Result = (Sum % 10 == 0);
            }
            return Result;
        }
        /// <summary>
        /// Saves the card number to the database.
        /// </summary>
        public void Save()
        {
            if (IsNew)
            {
                Db.AddToUserCreditCard(DataCard);
            }
            Db.SaveChanges();
            IsNew = false;
        }
        #endregion

        #region Static Methods
        internal static CreditCard GetCard(Data.UserCreditCard DataCard, Data.SharpDb Db)
        {
            CreditCard Result = null;
            switch (DataCard.CreditCardType.Type)
            {
                case ("American Express"):
                    {
                        Result = new AmericanExpress(Db, DataCard);
                        break;
                    }
                case ("VISA"):
                    {
                        Result = new Visa(Db, DataCard);
                        break;
                    }
                case ("Master Card"):
                    {
                        Result = new MasterCard(Db, DataCard);
                        break;
                    }
                case ("Discover"):
                    {
                        Result = new Discover(Db, DataCard);
                        break;
                    }
                default:
                    throw new ArgumentException("Unknown card type " + DataCard.CreditCardType.Type, "Type");
                    break;
            }
            return Result;
        }
        public static CreditCard GetCard(int ID)
        {
            CreditCard Result = null;
            Data.SharpDb Db = new SharpCart.Data.SharpDb();
            Data.UserCreditCard DataCard = Db.UserCreditCard.First(CC => CC.ID == ID);
            Result = GetCard(DataCard, Db);
            return Result;
        }
        #endregion
    }
}
